package com.zhanglinwei.openApiClient.client;

import com.alibaba.fastjson2.JSON;
import com.dtflys.forest.Forest;
import com.zhanglinwei.common.utils.AssertUT;
import com.zhanglinwei.openApiClient.model.entity.OpenApiEntity;
import com.zhanglinwei.openApiClient.model.request.OpenApiRequest;
import com.zhanglinwei.openApiClient.model.response.OpenApiResponse;
import com.zhanglinwei.openApiClient.model.response.lambda.LambdaOpenApiResponse;
import com.zhanglinwei.openApiClient.utils.OpenApiSigner;
import com.zhanglinwei.openApiClient.wrapper.AbstractLambdaWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.zhanglinwei.common.constants.StringPoolConstant.SLASH;

@Slf4j
public class DefaultOpenApiClient implements OpenApiClient {

    private final String serverAddress;
    private final String appKey;
    private final String appSecret;

    DefaultOpenApiClient(String serverAddress, String appKey, String appSecret) {
        AssertUT.notBlank(serverAddress, "Server address cannot be null or empty");
        AssertUT.notBlank(appKey, "App key and secret cannot be null or empty");
        AssertUT.notBlank(appSecret, "App key and secret cannot be null or empty");

        this.serverAddress = serverAddress.endsWith(SLASH) ? serverAddress.substring(0, serverAddress.length() - 1) : serverAddress;
        this.appKey = appKey;
        this.appSecret = appSecret;
    }

    @Override
    public <R extends OpenApiResponse> R apiCodeQuery(OpenApiRequest<R> request) {
        R response = Forest.post(serverAddress + "/apiCode/" + request.apiCode() + "/" + request.apiVersion())
                .addHeader(generateSignHeaderMap())
                .addBody(JSON.toJSONString(request)).contentTypeJson()
                .execute(request.responseType());

        return response;
    }

    @Override
    public <T, R extends LambdaOpenApiResponse<T>, E extends OpenApiEntity<R>> Optional<T> selectOne(AbstractLambdaWrapper<E, ? extends AbstractLambdaWrapper<E, ?>> lambdaWrapper) {
        return selectOne(lambdaWrapper, true);
    }

    @Override
    public <T, R extends LambdaOpenApiResponse<T>, E extends OpenApiEntity<R>> Optional<T> selectOne(AbstractLambdaWrapper<E, ? extends AbstractLambdaWrapper<E, ?>> lambdaWrapper, boolean throwError) {
        List<T> responseList = selectList(lambdaWrapper);
        if (CollectionUtils.isNotEmpty(responseList)) {
            AssertUT.isTrue(!throwError || responseList.size() == 1, "Expected one result (or null) to be returned by selectOne(), but found: " + responseList.size());
            return Optional.ofNullable(responseList.get(0));
        }
        return Optional.empty();
    }

    @Override
    public <T, R extends LambdaOpenApiResponse<T>, E extends OpenApiEntity<R>> List<T> selectList(AbstractLambdaWrapper<E, ? extends AbstractLambdaWrapper<E, ?>> lambdaWrapper) {
        R selectPage = selectPage(lambdaWrapper);
        return selectPage == null || selectPage.content() == null ? Collections.emptyList() : selectPage.content();
    }

    @Override
    public <T, R extends LambdaOpenApiResponse<T>, E extends OpenApiEntity<R>> R selectPage(AbstractLambdaWrapper<E, ? extends AbstractLambdaWrapper<E, ?>> lambdaWrapper) {
        AssertUT.notNull(lambdaWrapper, "lambdaWrapper can not be null or empty");
        AssertUT.isTrue(lambdaWrapper.isNotEmpty(), "lambdaWrapper can not be null or empty");
        AssertUT.notNull(lambdaWrapper.getEntityClass(), "Please call the conditional method or constructor to initialize the entity before the operation");

        Type returnType = ((ParameterizedTypeImpl) lambdaWrapper.getEntityClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
        return Forest.post(serverAddress + "/lambda/wrapper")
                .addHeader(generateSignHeaderMap())
                .addBody(JSON.toJSONString(lambdaWrapper)).contentTypeJson()
                .execute(returnType);

    }

    public Map<String, String> generateSignHeaderMap() {
        return OpenApiSigner.signMap(appKey, appSecret);
    }

}
